﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasicConsole
{
    using System;
    using System.IO;
    using System.Reflection;
    using ConnectBasic;

    enum ParseArgumentResult
    {
        Proceed,
        ShowUsage,
        Invalid
    }

    class Program
    {
        private const int ResultOk = 0;
        private const int ResultInvalidUsage = 1;
        private const int ResultMissingMain = 2;

        private string mPath;
        private string mName;
        private string mScript;
        private string[] mScriptArgs;
        private bool mLogo = true;

        #region Static Entry Point

        static int Main()
        {
            return (new Program()).Run();
        }

        #endregion

        #region Continue

        private int Run()
        {
            switch (this.ParseArguments())
            {
                case ParseArgumentResult.ShowUsage:
                    Logo();
                    return Usage(true);

                case ParseArgumentResult.Invalid:
                    Logo();
                    return Usage(false);
            }
      
            // Make sure the arguments we've got make sense
            if (mScript == null)
            {
                Logo();
                return Usage(false);
            }

            // Create a runtime and add the globals we need to it
            var environment = new ConsoleEnvironment(mScriptArgs);
            var runtime = BasicHelpers.CreateRuntime(false);
            runtime.Globals.SetVariable("ENVIRONMENT", environment); // Added in caps for case insensitivity

            // Compile the script
            var module = BasicHelpers.CompileStandardModule(mScript, runtime);

            // Find a "Main" method
            var main = module.GetFunction("Main");

            if (main == null)
            {
                return MissingMain();
            }

            // Show the logo if appropriate
            if (mLogo)
            {
                Logo();
            }

            // Execute it
            var target = main.GetCallableDelegate();
            var result = target.DynamicInvoke();

            if (target.Method.ReturnType == typeof(int))
            {
                return (int)result;
            }
            else
            {
                return ResultOk;
            }
        }

        #endregion

        #region Argument Parsing

        private ParseArgumentResult ParseArguments()
        {
            string[] arguments = Environment.GetCommandLineArgs();

            // First argument is the program path
            mPath = arguments[0];
            mName = Path.GetFileNameWithoutExtension(mPath);

            for (int i = 1; i < arguments.Length; i++)
            {
                string arg = arguments[i];

                if (arg.StartsWith("-") || arg.StartsWith("/"))
                {
                    switch (arg.Substring(1).ToUpperInvariant())
                    {
                        case "?":

                            // Show usage
                            return ParseArgumentResult.ShowUsage;

                        case "NOLOGO":
                        case "NL":

                            // No logo
                            mLogo = false;
                            break;

                        default:

                            // Unknown argument
                            return ParseArgumentResult.Invalid;
                    }
                }
                else
                {

                    // Must be the path to the script file
                    mScript = arg;

                    // All arguments after the script path are considered to be arguments to the script
                    var firstScriptArg = i + 1;
                    var scriptArgs = mScriptArgs = new string[arguments.Length - firstScriptArg];
                    Array.Copy(arguments, firstScriptArg, scriptArgs, 0, scriptArgs.Length);

                    // Don't do any more processing
                    return ParseArgumentResult.Proceed;
                }
            }

            return ParseArgumentResult.Proceed;
        }

        #endregion

        #region Messages

        private static void Logo()
        {
            Console.WriteLine("ConnectBasic [Version {0}]", GetAssemblyVersion());
            Console.WriteLine("Copyright (c) 2009 Philip Stears.  All rights reserved.");
            Console.WriteLine();
        }

        private static int MissingMain()
        {
            Console.Error.WriteLine("Error: Missing Main method");
            return ResultMissingMain;
        }

        private static int Usage(bool wasRequested)
        {
            if (wasRequested)
            {
                UsageCore(Console.Out);
                return ResultOk;
            }
            else
            {
                UsageCore(Console.Error);
                return ResultInvalidUsage;
            }
        }

        private static void UsageCore(TextWriter console)
        {
            console.WriteLine("cbx -?");
            console.WriteLine("cbx [Options] [PathToScriptFile] [ScriptArguments]");
        }

        #endregion

        #region Helpers

        private static string GetAssemblyVersion()
        {
            return ((AssemblyFileVersionAttribute)Attribute.GetCustomAttribute(typeof(Program).Assembly, typeof(AssemblyFileVersionAttribute))).Version;
        }

        #endregion

    }
}

